package test;

import acm.graphics.*;
import acm.program.*;
import acm.util.*;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class Breakout extends GraphicsProgram implements MouseListener {

    /**
     * Width and height of application window in pixels
     */
    public static final int APPLICATION_WIDTH = 400;
    public static final int APPLICATION_HEIGHT = 600;
    /**
     * Dimensions of game board (usually the same)
     */
    private static final int WIDTH = APPLICATION_WIDTH;
    private static final int HEIGHT = APPLICATION_HEIGHT;
    /**
     * Dimensions of the paddle
     */
    private static final int PADDLE_WIDTH = 60;
    private static final int PADDLE_HEIGHT = 10;
    /**
     * Offset of the paddle up from the bottom
     */
    private static final int PADDLE_Y_OFFSET = 30;
    /**
     * Number of bricks per row
     */
    private static final int NBRICKS_PER_ROW = 10;
    /**
     * Number of rows of bricks
     */
    private static final int NBRICK_ROWS = 10;
    /**
     * Separation between bricks
     */
    private static final int BRICK_SEP = 4;
    /**
     * Width of a brick
     */
    private static final int BRICK_WIDTH =
            (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
    /**
     * Height of a brick
     */
    private static final int BRICK_HEIGHT = 8;
    /**
     * Radius of the ball in pixels
     */
    private static final int BALL_RADIUS = 10;
    /**
     * Offset of the top brick row from the top
     */
    private static final int BRICK_Y_OFFSET = 70;
    /**
     * Number of turns
     */
    private static final int NTURNS = 3;
    /**
     * the starting pause delay between paddle hits *
     */
    private static final int PAUSE_DELAY = 20;
    /**
     * text size of the display text
     */
    private static final int TEXT_SIZE = 5;
    /**
     * ball speed
     */
    private static final double BALL_SPEED = 3.0;
    /**
     * extras - if you catch the extra score, this is the extra value
     */
    private static final int EXTRA_SCORE_VALUE = 100;
    /**
     * extras speed coming down the screen
     */
    private static final double EXTRAS_SPEED = 3.0;

    public void mouseMoved(MouseEvent e) {
        Point movePos = e.getPoint();
        if (movePos.x < (WIDTH - PADDLE_WIDTH)) {
            paddleObject.setLocation(movePos.x, HEIGHT - (PADDLE_Y_OFFSET + PADDLE_HEIGHT));
        }
    }

    // setup the bricks, with different colours
    private void CreateBricks() {
        colours[0] = Color.RED;
        colours[1] = Color.RED;
        colours[2] = Color.ORANGE;
        colours[3] = Color.ORANGE;
        colours[4] = Color.YELLOW;
        colours[5] = Color.YELLOW;
        colours[6] = Color.GREEN;
        colours[7] = Color.GREEN;
        colours[8] = Color.CYAN;
        colours[9] = Color.CYAN;

        int startX, startY = BRICK_Y_OFFSET;
        GRect bricks;
        // iRow = going downwards
        for (int iRow = 0; iRow < NBRICK_ROWS; iRow++) {
            startX = BRICK_SEP;
            // iAisle = going across
            for (int iAisle = 0; iAisle < NBRICKS_PER_ROW; iAisle++) {
                bricks = new GRect(startX, startY, BRICK_WIDTH, BRICK_HEIGHT);
                bricks.setFillColor(colours[iRow]);
                bricks.setFilled(true);
                bricks.setColor(colours[iRow]);
                add(bricks);
                startX += BRICK_WIDTH + BRICK_SEP;
            }
            startY += BRICK_HEIGHT + BRICK_SEP;
        }
    }

    private void addPaddle() {
        paddleObject = new GRect((WIDTH / 2) - (PADDLE_WIDTH / 2), HEIGHT - (PADDLE_Y_OFFSET + PADDLE_HEIGHT),
                PADDLE_WIDTH, PADDLE_HEIGHT);
        paddleObject.setColor(Color.BLACK);
        paddleObject.setFillColor(Color.BLACK);
        paddleObject.setFilled(true);
        add(paddleObject);
    }

    private void setUpBall() {
        // middle !!
        ballX = WIDTH / 2;
        ballY = HEIGHT / 2;
        // ballVX / VY are the velocity of movement 
        ballObject = new GOval(ballX, ballY, BALL_RADIUS, BALL_RADIUS);
        ballObject.setFillColor(Color.BLACK);
        ballObject.setFilled(true);
        add(ballObject);
        // setup the ball to move a random direction
        ballVX = rgen.nextDouble(1.0, 3.0);
        if (rgen.nextBoolean(0.5)) {
            ballVX -= ballVX;
        }
        ballVY = BALL_SPEED;
    }

    // move the ball and check for collision around the screen, if gone to the bottom return true else false
    private Boolean moveBall() {
        GPoint ballPoint = ballObject.getLocation();
        if (ballPoint.getX() > WIDTH) {
            ballVX = -ballVX;
        }
        if (ballPoint.getX() <= 0) {
            ballVX = -ballVX;
        }
        if (ballPoint.getY() <= 0) {
            ballVY = -ballVY;
        }
        if (ballPoint.getY() > HEIGHT) {
            return true;//ballVY = -ballVY; // basically lost
        }
        ballObject.move(ballVX, ballVY);
        return false;
    }

    // get any Graphics Object surrounding the ball Object, be it the bat and ball, or bat and extras
    private GObject getCollidingObject(GObject ballObj) {
        if (!ballObj.isVisible()) {
            return null;
        }
        GPoint ballPoint = ballObj.getLocation();
        GObject coll;
        Point addingPoints[] = new Point[4];
        addingPoints[0] = new Point(0, 0);
        addingPoints[1] = new Point(0, BALL_RADIUS);
        addingPoints[2] = new Point(BALL_RADIUS, BALL_RADIUS);
        addingPoints[3] = new Point(BALL_RADIUS, 0);
        for (int i = 0; i < 4; i++) {
            coll = getElementAt(ballPoint.getX() + addingPoints[i].x, ballPoint.getY() + addingPoints[i].y);
            if (coll != null) {
                return coll;
            }
        }
        return null;
    }

    // display the text on the screen to give some information.
    private void displayText(String text) {
        textDisplay = new GLabel(text, (WIDTH / 2 - (text.length() * TEXT_SIZE)), HEIGHT / 2);
        textDisplay.setFont(Font.SANS_SERIF);
        add(textDisplay);
    }

    // display the status , score and also lives left
    private void displayStatus() {
        if (status != null) {
            remove(status);
        }
        status = new GLabel("Score : " + score + " : Lives : " + lifesLeft, WIDTH / 2, (BRICK_HEIGHT * 2));
        add(status);
    }

    // difference between two points.
    private GPoint differenceBetween(GPoint a, GPoint b) {
        return new GPoint(a.getX() - b.getX(), a.getY() - b.getY());
    }

    // type = true - extra life, false - extra score (+100) 
    private void generateExtra(Boolean type, GObject startingPoint) {
        if (type) {
            // create a extraLife object if not one already
            extraLife.setLocation(startingPoint.getX(), startingPoint.getY());
            extraLife.setColor(Color.MAGENTA);
            extraLife.setFilled(true);
            extraLife.setVisible(true);
            add(extraLife);
        } else {
            // create a extraScore object if not one already
            extraScore.setLocation(startingPoint.getX(), startingPoint.getY());
            extraScore.setColor(Color.GRAY);
            extraScore.setFilled(true);
            extraScore.setVisible(true);
            add(extraScore);
        }
    }

    /*
     * move the extraLife/Score if they are visible.
     */
    private void moveExtras() {
        if (extraLife.isVisible()) {
            extraLife.move(0, EXTRAS_SPEED);
            if (extraLife.getLocation().getY() > HEIGHT) {
                remove(extraLife);
            }
        }
        if (extraScore.isVisible()) {
            extraScore.move(0, EXTRAS_SPEED);
            if (extraScore.getLocation().getY() > HEIGHT) {
                remove(extraScore);
            }
        }
    }
    /*
     * Method: run()
     */

    /**
     * Runs the Breakout program.
     */
    public void run() {
        /*
         * You fill this in, along with any subsidiary methods
         */
        getGCanvas().setSize(WIDTH, HEIGHT);
        GObject colliding;
        final AudioClip bounceSound = MediaTools.loadAudioClip("bounce.au");
        Boolean bottomHit, emptyBricks = true;
        int bricksLeft = 0, paddleHit, randomExtra;

        extraLife = new GOval(0, 0, BALL_RADIUS, BALL_RADIUS);
        extraScore = new GOval(0, 0, BALL_RADIUS, BALL_RADIUS);
        score = 0;
        lifesLeft = NTURNS;
        pauseDelay = PAUSE_DELAY;
        // loop through for the amount of lives
        while (lifesLeft > 0) {
            /*
             * setup the display and then ask the user to click to start
             */

            if (emptyBricks) {
                /*
                 * clear the screen and rebuild the bricks, if come to the end
                 * of that level, or just starting
                 */
                removeAll();
                bricksLeft = NBRICKS_PER_ROW * NBRICK_ROWS;
                CreateBricks();
                addPaddle();
                addMouseListeners();
                emptyBricks = false;
            }
            displayText("Click to start");
            displayStatus();
            waitForClick();
            remove(textDisplay);
            setUpBall();
            paddleHit = 0;
            bottomHit = false;
            randomExtra = rgen.nextInt(5);
            /*
             * end of setup display
             */
            while (true) {
                // moveBall returns true if the bottom has been hit.
                if (moveBall()) {
                    bottomHit = true;
                    break;
                }
                moveExtras();
                colliding = getCollidingObject(ballObject);
                // if the ball has collided with anything
                if (colliding != null) {
                    if (!colliding.equals(paddleObject)) {
                        if (!colliding.equals(status)) {
                            if (randomExtra <= 0) {
                                randomExtra = rgen.nextInt(5);
                                // create a extra life if next random extra is even else extra score
                                if ((randomExtra % 2) == 0) {
                                    generateExtra(true, colliding);
                                } else {
                                    generateExtra(false, colliding);
                                }
                            }
                            // delete the object that we just collided with, e.g. the brick
                            if (colliding.getClass().getSimpleName().equalsIgnoreCase("GRect")) {
                                // decrement the randomExtra 
                                randomExtra--;
                                // increment the score by finding out the colour of brick that we hit.
                                Color collidColor = colliding.getColor();
                                for (int i = (NBRICK_ROWS - 1); i > 0; i--) {
                                    score++;
                                    if (colours[i] == collidColor) {
                                        break;
                                    }
                                }
                                displayStatus();
                                remove(colliding);
                                /*
                                 * if no more bricks.. exit
                                 */
                                bricksLeft--;
                                if (bricksLeft == 0) {
                                    emptyBricks = true;
                                    break;
                                }
                                /*
                                 * //another way of checking to see if there is
                                 * any GRects left.!!. emptyBricks = true; for
                                 * (int i =0; i < getElementCount(); i++) { if
                                 * (getElement(i).getClass().getSimpleName().equalsIgnoreCase("GRect"))
                                 * { if
                                 * (!getElement(i).getClass().equals(paddleObject))
                                 * { emptyBricks= false; break; } } } if
                                 * (emptyBricks) break;
                                 */
                                ballVY = -ballVY;
                            }
                        }
                    } else {
                        // handle the increase of speed (less delay)
                        paddleHit++;
                        if ((paddleHit % 7) == 0) {
                            pauseDelay--;
                        }

                        new Thread() {

                            @Override
                            public void run() {
                                bounceSound.play();
                            }
                        }.start();

                        GPoint difBetween = differenceBetween(ballObject.getLocation(), colliding.getLocation());
                        ballVX = (difBetween.getX() - (PADDLE_WIDTH / 2)) / 2;
                        ballVY = -(difBetween.getY() / BALL_SPEED);
                        // for when the ball may try and bounce of the side of the bat and then the wall!!.
                        if (ballVY == 0.0) {
                            ballVY = 1.0;
                        }
                        // move in the opposite direction
                        ballVY = -ballVY;
                    }
                }
                /*
                 * extra items
                 */
                colliding = getCollidingObject(extraLife);
                if (colliding != null) {
                    if (colliding.equals(paddleObject)) {
                        extraLife.setVisible(false);
                        lifesLeft++;
                        displayStatus();
                    }
                }
                colliding = getCollidingObject(extraScore);
                if (colliding != null) {
                    if (colliding.equals(paddleObject)) {
                        extraScore.setVisible(false);
                        score += EXTRA_SCORE_VALUE;
                        displayStatus();
                    }
                }
                pause(pauseDelay);
            }
            if (bottomHit) {
                lifesLeft--;
                displayStatus();
                if (lifesLeft > 0) {
                    displayText("Bottom hit : lives left = " + lifesLeft);
                } else {
                    displayText("End of Game : your score " + score);
                }
            }
            if (emptyBricks) {
                displayText("Yeppy you do it!! click to start again");
            }
            waitForClick();
            remove(textDisplay);
        }
    }
    private GRect paddleObject;
    private GOval ballObject, extraScore, extraLife;
    private GLabel textDisplay, status;
    private double ballVX, ballVY, ballX, ballY;
    private int lifesLeft, score, pauseDelay;
    private RandomGenerator rgen = RandomGenerator.getInstance();
    private Color colours[] = new Color[NBRICK_ROWS];
}
